package cn.tangcent;

import java.util.*;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Consumer;

/**
 * Created by TangMing on 2017/4/8.
 */
public class SudokuData {
    private volatile int[][] elements;//9*9
    private volatile int[][] optionals;//9*9
    private volatile int[] sx;//x1-9
    private volatile int[] sy;//y1-9
    private volatile int[] sz;//z1-9;
    private List<Integer> rests = new ArrayList<>();
    private ReadWriteLock lock = new ReentrantReadWriteLock();

    public SudokuData(int[][] elements) {
        this.elements = elements;
        init();
    }

    private void init() {
        sx = new int[10];
        Arrays.fill(sx, 0);
        sy = new int[10];
        Arrays.fill(sy, 0);
        sz = new int[10];
        Arrays.fill(sz, 0);
        optionals = new int[9][9];
        SudokuUtils.each(optionals, (val, x, y) -> {
            rests.add(SudokuUtils.toIndex(x, y));
            return SudokuUtils.ALL;
        });
        SudokuUtils.each(elements, (val, x, y) -> {
            if (val != 0) {
                sure(val, x, y);
            }
            return val;
        });
    }

    public void sure(Value value) {
        sure(value.getNum(), value.getX(), value.getY());
    }

    public boolean completed() {
        lock.readLock().lock();
        try {
            return rests.isEmpty();
        } finally {
            lock.readLock().unlock();
        }
    }

    private void sure(int val, int x, int y) {
        if (todo(x, y)) {
            lock.writeLock().lock();
            try {
                if (todo(x, y)) {
                    this.elements[x][y] = val;
                    int box = SudokuUtils.box(val);
                    this.optionals[x][y] = box;
                    sx[x] |= box;
                    sy[y] |= box;
                    sz[SudokuUtils.toZ(x, y)] |= box;
                    int complement = ~box;
                    SudokuUtils.sibling(x, y, coordinate -> {
                        this.optionals[coordinate.getX()][coordinate.getY()] &= complement;
                    });
                    rests.remove(new Integer(SudokuUtils.toIndex(x, y)));
                }
            } finally {
                lock.writeLock().unlock();
            }
        }
    }

    public void ensure(Coordinate[] ensureEles, int options) {
        lock.writeLock().lock();
        try {
            for (Coordinate ensureEle : ensureEles) {
                optionals[ensureEle.getX()][ensureEle.getY()] &= ~options;
            }
        } finally {
            lock.writeLock().unlock();
        }
    }

    //确定此位置上不可使用此选项
    public void ensure(int x, int y, int options) {
        lock.writeLock().lock();
        try {
            optionals[x][y] &= ~options;
        } finally {
            lock.writeLock().unlock();
        }
    }

    public int sx(int x) {
        lock.readLock().lock();
        try {
            return sx[x];
        } finally {
            lock.readLock().unlock();
        }
    }

    public int sy(int y) {
        lock.readLock().lock();
        try {
            return sy[y];
        } finally {
            lock.readLock().unlock();
        }
    }

    public int sz(int z) {
        lock.readLock().lock();
        try {
            return sz[z];
        } finally {
            lock.readLock().unlock();
        }
    }

    public int element(int x, int y) {
        lock.readLock().lock();
        try {
            return elements[x][y];
        } finally {
            lock.readLock().unlock();
        }
    }

    public int optional(Coordinate coordinate) {
        return optional(coordinate.getX(), coordinate.getY());
    }

    public int optional(int x, int y) {
        lock.readLock().lock();
        try {
            return optionals[x][y];
        } finally {
            lock.readLock().unlock();
        }
    }

    public Integer[] optionals(Coordinate[] coordinates) {
        lock.readLock().lock();
        try {
            return Arrays.stream(coordinates).map(this::optional).<Integer>toArray(Integer[]::new);
        } finally {
            lock.readLock().unlock();
        }
    }

    //保留需要填的位置
    public Coordinate[] todos(Coordinate[] coordinates) {
        lock.readLock().lock();
        try {
            return Arrays.stream(coordinates).filter(coordinate -> rests.contains(SudokuUtils.toIndex(coordinate))).toArray(Coordinate[]::new);
        } finally {
            lock.readLock().unlock();
        }
    }

    public boolean todo(int x, int y) {
        lock.readLock().lock();
        try {
            return rests.contains(SudokuUtils.toIndex(x, y));
        } finally {
            lock.readLock().unlock();
        }
    }

    public List<Integer> rests() {
        lock.readLock().lock();
        try {
            return new ArrayList<>(rests);
        } finally {
            lock.readLock().unlock();
        }
    }

    public int[][] result() {
        int[][] copy = new int[9][];
        for (int i = 0; i < 9; i++) {
            copy[i] = Arrays.copyOf(elements[i], 9);
        }
        return copy;
    }

}
